VersionBumper.php

<?php

namespace Tlf\Scrawl;

/**
 * Bumps versions. This implementation may be moved to its own, separate library. Idk.
 * 
 */
class VersionBumper {

    /**
     * Use system git commands to determine current version from the branch name and available tags.
     *
     * You should run `git fetch` before using this for accurate results.
     *
     * @param $bump_rules array<int index_in_version_string, array string_prefixes> string prefixes are literal, except a single asterisk (`*`) will always bump that version number, and should only be present if you want a default case of bumping the last index
     *
     * @return string version like 0.8.0.1 from 0.8.0.0
     */
    public function get_new_version_from_git(array $bump_rules): string {
        ob_start();
        system("git log -1 --pretty=%B");
        $last_commit = ob_get_clean();
        $branch = $this->get_current_branch();
        $branch_version = $this->get_branch_version($branch);
        $latest_tag = $this->get_latest_tag($branch_version);

        echo "\nLatest Tag: $latest_tag";

        $new_version = $this->get_new_version($latest_tag, $last_commit, $bump_rules);

        if ($new_version == $latest_tag){
            echo "\n\nFailed to bump version. \nLast Commit: $last_commit\n\nbump rules: \n";
            print_r($bump_rules);
            throw new \Exception("Failed to bump version");
        }

        return $new_version;
    }


    /**
     * Get a bumped version number. Returns `$latest_version` if none of the `$bump_rules` apply.
     *
     * Ex: 0.8.0 gets commit 'bugfix: something', and bumps to 0.8.0.1 based on the bump rules provided.
     * Ex: 0.8.0.2 gets commit 'feature: cool stuff', and bumps to 0.8.1.0 based on the bump rules provided.
     * Ex: 0.8.0 gets commit 'notes', and does not bump, so '0.8.0' is returned.
     *
     * @param $latest_version string of numbers separated by periods, like '3.8.1'
     * @param $latest_commit_msg string with a prefix that indicates which portion of the version string to bump (increase)
     * @param $bump_rules array<int index_in_version_string, array string_prefixes> string prefixes are literal, except a single asterisk (`*`) will always bump that version number, and should only be present if you want a default case of bumping the last index
     *
     * @return string representing a new version number. This may be the SAME as $latest_version if no $bump_rules passed.
     */
    public function get_new_version(string $latest_version, string $latest_commit_msg, array $bump_rules): string {

        $index_to_bump = $this->get_index_to_bump($latest_commit_msg, $bump_rules);
        $i = $index_to_bump;

        $parts = explode(".", $latest_version);
        $length = count($parts);
        while (($length = count($parts)) <= $index_to_bump){
            $parts[] = '0';
        }

        if ($i>=0){
            $parts[$i] = ((int)$parts[$i])+1;
            while (++$i < $length){
                $parts[$i] = 0;
            }
        }


        $new_version = implode(".", $parts);

        return $new_version;
    }

    /**
     * @return int index to increase by 1, or -1 if there should not be a version increase.
     */
    public function get_index_to_bump(string $commit_message, array $index_rules): int{

        foreach ($index_rules as $index_to_bump => $prefixes_to_cause_bump){
            foreach ($prefixes_to_cause_bump as $prefix){
                if ($prefix=='*')return $index_to_bump;
                $len = strlen($prefix);
                if (substr($commit_message,0,$len+1) == "$prefix:"){
                    return $index_to_bump;
                }
            }
        }

        return -1;
    }

    /**
     *
     * @param $version_prefix string like "1.0" or "0.8"
     */
    public function get_latest_tag(string $version_prefix=''): string{

        // TODO: If 0.8.0 exists & we have 0.8.0.1, ... getting the latest tag doesn't work right
        // I tried to fix it, but I think I made it worse

        ob_start();
        system("git tag --list \"$version_prefix.*\"");
        $available_versions = ob_get_clean();
        
        

        $tags = explode("\n", trim($available_versions));

        $max_index_version = -1;
        $len = strlen($version_prefix);
        $latest_tag = null;
        foreach ($tags as $full_version){
            if ($len > 0)$target_version = substr($full_version,$len);
            else $target_version = $target_version = $full_version;

            echo "\n\nTest: $target_version";

            $parts = explode('.', $target_version);
            if (count($parts)==0)continue;
            else if (((int)$parts[0]) > $max_index_version){
                $max_index_version = (int)array_shift($parts);
                $postfix_version = implode('.', $parts);
                $latest_tag = trim("$version_prefix.$max_index_version");
            }
        } 

        if ($latest_tag == null){
            throw new \Exception("Could not find a matching tag for version prefix '$version_prefix'");
        }

        var_dump($latest_tag);
        exit;

        return $latest_tag;
    }

    /**
     * Return the name of the current git branch
     *
     */
    public function get_current_branch(): string {
        return exec("git rev-parse --abbrev-ref HEAD");
    }

    /**
     * Assumes branch name starts with 'v', removes it, and returns the remaining string, assumed to be numbers separated by periods.
     *
     * @return string like 1.0
     */
    public function get_branch_version(string $branch_name): string {
        return substr($branch_name,1);
    }



}